from typing import List

def __join__(l):
    return '{ ' + ' | '.join('None' if x is None else str(x) for x in l) + ' }'

def __input__(
    required        : bool      = True,
    input_info      : str       = None,
    default_value   : str       = None,
    optionals       : List[str] = None,
    help_message    : str       = None,
    input_message_extender      = None,
    validator                   = None,
    convertor                   = None,
    **kwargs
) -> str:
    if input_info is None or len(input_info) == 0:
        raise ValueError(f'parameter input info can not be None or blank.')

    if optionals is not None and default_value is not None and default_value not in optionals:
        raise ValueError(f'default value: [{default_value}] is not in optionals: {__join__(optionals)}')

    if help_message:
        print(help_message)

    input_message = f'{input_info}{" [required]" if required else ""} [default: {default_value}]'
    if optionals:
        input_message += f' [ optionals: {__join__(optionals)}]'
    if input_message_extender is not None:
         input_message += input_message_extender(required, input_info, default_value, optionals, **kwargs)
    input_message += ' : '

    while True:
        try:
            user_input = input(input_message)

            if len(user_input) == 0:
                if default_value is not None:
                    result = default_value if convertor is None else convertor(default_value)
                    break
                if optionals:
                    result = optionals[0] if convertor is None else convertor(optionals[0])
                    break
                if required:
                    print(f'error: it is required.')
                    continue
                result = None
                break
            else:
                if optionals and user_input not in optionals:
                    print(f'error: your input: [{user_input}]. it is not in the optionals: {__join__(optionals)}')
                    continue
                
                if validator is not None:
                    r = validator(user_input, **kwargs)
                    if r['flag']:
                        result = r['value']
                        break
                    print(f'error: your input: [{user_input}]. {r["message"]}')
                    continue

            result = user_input
            break
        except KeyboardInterrupt:
            result = None
            break

    print('>', result)
    return result

def __str_input_message_extender__(required, input_info, default_value, optionals, **kwargs):
    if optionals: return ''

    message = ''

    startswith = kwargs['startswith']
    if startswith is not None:
        if isinstance(startswith, str):
            message += f' [startswith: {startswith}]'
        else:
            message += f' [startswith: {__join__(startswith)}]'

    endswith   = kwargs['endswith']
    if endswith is not None:
        if isinstance(endswith, str):
            message += f' [endswith: {endswith}]'
        else:
            message += f' [endswith: {__join__(endswith)}]'

    return message

def __str_validator__(user_input, **kwargs):
    startswith = kwargs['startswith']
    if startswith is not None:
        if isinstance(startswith, str):
            if not user_input.startswith(startswith):
                return { 'flag': False, 'message': f'it is not starts with [{startswith}].'}
        else:
            flag = False
            for s in startswith:
                if user_input.startswith(s):
                    flag = True
                    break
            if not flag:
                return { 'flag': False, 'message': f'it is not starts with {__join__(startswith)}'}

    endswith = kwargs['endswith']
    if endswith is not None:
        if isinstance(endswith, str):
            if not user_input.endswith(endswith):
                return { 'flag': False, 'message': f'it is not ends with [{endswith}].'}
        else:
            flag = False
            for e in endswith:
                if user_input.endswith(e):
                    flag = True
                    break
            if not flag:
                return { 'flag': False, 'message': f'it is not ends with {__join__(endswith)}'}

    return { 'flag': True, 'value': user_input}

def input_str(
    required        : bool              = True,
    input_info      : str               = None,
    default_value   : str               = None,
    optionals       : List[str]         = None,
    help_message    : str               = None,
    
    startswith      : (str, List[str])  = None,
    endswith        : (str, List[str])  = None,
) -> str:
    return __input__(
        required        = required,
        input_info      = input_info,
        default_value   = default_value,
        optionals       = optionals,
        help_message    = help_message,
        
        input_message_extender  = __str_input_message_extender__,
        validator       = __str_validator__,
        
        startswith      = startswith,
        endswith        = endswith,
    )

def input_bool(
    required        : bool      = True,
    input_info      : str       = None,
    default_value   : bool      = True,
    optionals       : List[str] = None,
    help_message    : str       = None,
) -> str:
    result = __input__(
        required        = required,
        input_info      = input_info,
        optionals       = ['y', 'n'],
        default_value   = 'y' if default_value else 'n',
        help_message    = help_message,
    )
    return True if result == 'y' else False

def __int_input_message_extender__(required, input_info, default_value, optionals, **kwargs):
    if optionals: return ''

    range_min = kwargs['range_min']
    range_max = kwargs['range_max']

    return f' [range: from {range_min if range_min is not None else "-∞"} to {range_max if range_max is not None else "+∞"}]'

def __int_validator__(user_input, **kwargs):
    try:
        i = int(user_input)
    except:
        return { 'flag': False, 'message': f'it is not a integer.'}

    range_min = kwargs['range_min']
    if range_min is not None and i < range_min:
        return { 'flag': False, 'message': f'it is less than minimum value [{range_min}].'}

    range_max = kwargs['range_max']
    if range_max is not None and i > range_max:
        return { 'flag': False, 'message': f'it is more than maximum value [{range_max}].'}

    return { 'flag': True, 'value': i}

from kinyx.type_util import str_to_int
def input_int(
    required        : bool      = True,
    input_info      : str       = None,
    default_value   : int       = None,
    optionals       : List[int] = None,
    help_message    : str       = None,
    
    range_min       : int       = None,
    range_max       : int       = None,
) -> str:
    if (range_min is not None or range_max is not None) and optionals is not None:
        raise ValueError('parameter [optionals] has been specified, parameter [range min] or parameter [range max] cannot be specified simultaneously.')
    if range_min is not None and range_max is not None and range_min > range_max:
        raise ValueError('parameter [range min] can not large than parameter [range max].')
    
    return __input__(
        required                = required,
        input_info              = input_info,
        optionals               = None if optionals is None else [None if optional is None else str(optional) for optional in optionals],
        default_value           = None if default_value is None else str(default_value),
        help_message            = help_message,
        
        input_message_extender  = __int_input_message_extender__,
        convertor               = str_to_int,
        validator               = __int_validator__,
        
        range_min               = range_min,
        range_max               = range_max,
    )